/*
 * Context.cs
 *
 * Created on November 17, 2007, 4:38 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 * Copyright (C) 2010       Mathijs Miermans
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace HexTD
{
	public enum direction
	{

		NORTH,
		EAST,
		SOUTH,
		WEST,
		UP,
		DOWN,
		LEFT,
		RIGHT
	}

	public enum gameState
	{

		playing,
		editing,
		selecing,
		replaying,
		checking
	}

	/**
	 *
	 * @author hylke
	 */
	public class Context
	{
		public string levelTitle = "";
		public string levelInfo = "";
		public string backgroundImage = "images\\sunset.jpg";
		public Object PaintingLock = new Object();
		public Object gameTimeLock = new Object();
		public int gameTime;
		private HexTD mainApp;
		public Game game;
		public string VERSION;
#if PORT
		public BlackBox blackBoxCurrent;
#endif
		public string contextUrl;
		public string mapContextUrl;
		public ColorSelection colors;
		public Hex[,] grid;
		public int gridWidth;
		public int gridHeight;
		public float offsetX;
		public float offsetY;
		public float minX, minY, maxX, maxY;
		public float paintScale;
		public float strokeScale;
		public float hexPaintWidth;
		public float hexFloatWidth;
		public gameState gameState = gameState.selecing;
		public string mapLocation;
		public string mapMD5;
		public EnemySprite[] enemies;
		public List<Tower> towers;
		private int enemyCount = 0;
		private List<Path> paths;
		private int wave = 0;
		private List<Wave> waves;
		private bool waveActive = false;
		private int lives = 20;
		public int livesStart = 20;
		private long credits = 0;
		public long creditsStart = 0;
		public int interestStart = 3;
		public int interestIncrease = 3;
		private int bonus = 0;
		private int bonusWait = 0;
		private long score = 0;

		// Events
		public event EventHandler waveStarted;
		public event EventHandler moneyChanged;
		public event EventHandler livesChanged;
		public event EventHandler interestChanged;
		public event EventHandler towerRemoved;
		public event EventHandler towerBuild;
#if PORT_REM
		private List<WaveStartListener> waveListeners = new List<WaveStartListener>();
#endif
#if PORT
		private SVGUniverse svgUniverse;
#endif
		private Dictionary<string, Uri> svgImages;
#if PORT_REM
		private Dictionary<string, StyleSheet> styleSheets;
		private Dictionary<string, Image> images;
		private Cache cache;
#endif

		public Context(HexTD mainApp, Game game)
		{
			this.mainApp = mainApp;
			this.game = game;
			this.colors = new ColorSelection();
#if PORT
			this.styleSheets = new Dictionary<string, StyleSheet>();
#endif
			this.svgImages = new Dictionary<string, Uri>();
#if PORT_REM
			this.images = new Dictionary<string, Image>();
#endif
			this.towers = new List<Tower>();
			this.paths = new List<Path>();
			this.waves = new List<Wave>();
#if PORT_REM
			this.cache = new Cache();
#endif
		}

		public bool loadMap(Stream mapStream, string mapLocation)
		{
			this.resetMap();
			bool retval = MapIO.readLevel(this, mapStream, mapLocation);
#if PORT
			this.mainApp.mapChanged();
#endif
			return retval;
		}

#if PORT_REM
		public void loadMap(string fetchUrl, string relativeLoc)
		{
			this.resetMap();
			MapIO.readLevel(this, fetchUrl, relativeLoc);
			this.mainApp.mapChanged();
		}

		public void loadMap(TextReader b, string maplocation)
		{
			this.resetMap();
			MapIO.readLevel(this, b, maplocation);
			this.mainApp.mapChanged();
		}
#endif

		public void resetMap()
		{
			this.removeAllEnemies();
			this.clearTowers();
			this.setCredits(this.creditsStart);
			this.setInterest(this.interestStart, this.interestIncrease);
			this.resetLives(this.livesStart);
			this.resetBonus();
			this.resetScore();
			this.resetWaves();
			this.gameTime = 0;
#if PORT
			this.mainApp.resetMap();
#endif
		}

		public void highlightHex(int[] hexC)
		{
#if PORT
			this.mainApp.highLightHex(hexC);
#endif
		}

		public Hex getHex(int col, int row)
		{
			return this.grid[col, row];
		}

		public void setEnemyCount(int c)
		{
			this.enemyCount = c;
		}

		public int getEnemyCount()
		{
			return this.enemyCount;
		}

		public void removeEnemy()
		{
			this.enemyCount--;
#if PORT
			this.mainApp.enemyDied(this.enemyCount);
#endif
			if (this.enemyCount == 0)
			{
				this.payInterest();
				if (this.wave >= this.waves.Count)
				{
#if PORT
					this.blackBoxCurrent.endGame();
#endif
				}
			}
		}

		public void removeAllEnemies()
		{
			if (this.enemyCount > 0)
			{
				this.enemyCount = 0;
				this.enemies = new EnemySprite[0];
#if PORT
				this.mainApp.enemyDied(this.enemyCount);
#endif
			}
		}

		public EnemySprite findClosestCreep(float paintX, float paintY, float maxRange)
		{
			EnemySprite esFound = null;
			float maxR2 = maxRange * maxRange;
			float closestR2 = float.MaxValue;
			float r2, dx, dy;
			if (this.enemies != null)
			{
				foreach (EnemySprite es in this.enemies)
				{
					if (!es.isDead())
					{
						dx = es.getPaintX() - paintX;
						dy = es.getPaintY() - paintY;
						r2 = dx * dx + dy * dy;
						if (r2 < closestR2)
						{
							esFound = es;
							closestR2 = r2;
						}
					}
				}
				if (closestR2 < maxR2)
				{
					return esFound;
				}
				else
				{
					return null;
				}
			}
			else
			{
				return null;
			}
		}

		// Wave handling methods
		public void clearWaves()
		{
			this.wave = 0;
			this.waves.Clear();
		}

		public void resetWaves()
		{
			this.wave = 0;
		}

		public List<Wave> getWaves()
		{
			return this.waves;
		}

		public int getWave()
		{
			return this.wave;
		}

		public int getWaveCount()
		{
			return this.waves.Count;
		}

		public void nextWave()
		{
			System.Diagnostics.Debug.WriteLine("Sending next wave: " + this.wave);
			Wave w = this.waves[this.wave];
			this.enemies = w.getEnemies();
			for (int i = 0; i < this.enemies.Length; i++)
			{
				this.enemies[i].resetScale();
			}
			this.wave++;
			this.waveActive = true;
			this.setEnemyCount(w.enemyCount());
			this.fireWaveStartedEvent();
		}

#if PORT_REM
		public void addWaveStartListener(WaveStartListener l)
		{
			this.waveListeners.Add(l);
		}

		public void removeWaveStartListener(WaveStartListener l)
		{
			this.waveListeners.Remove(l);
		}
#endif

		#region EventTriggering
		private void fireWaveStartedEvent()
		{
			if (waveStarted != null)
			{
				waveStarted(this, null);
			}
		}

		private void fireMoneyChangedEvent()
		{
			if (moneyChanged != null)
			{
				moneyChanged(this, null);
			}
		}


		private void fireLivesChangedEvent()
		{
			if (livesChanged != null)
			{
				livesChanged(this, null);
			}
		}


		private void fireInterestChangedEvent()
		{
			if (interestChanged != null)
			{
				interestChanged(this, null);
			}
		}


		private void fireTowerRemovedEvent(Tower tower)
		{
			if (towerRemoved != null)
			{
				towerRemoved(this, (EventArgs)tower);
			}
		}


		private void fireTowerBuildEvent(Tower tower)
		{
			if (towerBuild != null)
			{
				towerBuild(this, (EventArgs)tower);
			}
		}
		#endregion

		private void payInterest()
		{
			long interest = (long)(this.credits * (this.interestStart + this.bonus * this.interestIncrease) / 100L);
			this.bonus += this.bonusWait;
			this.bonusWait = 0;
			this.credits += interest;
			this.score += interest;
			this.fireMoneyChangedEvent();
		}

		public void setInfoText(string s)
		{
#if PORT
			this.mainApp.setInfoText(s);
#endif
		}

		public void addScore(int s)
		{
			if (s < 0)
			{
				System.Diagnostics.Debug.WriteLine("Context::addScore: Adding negative score? " + s);
			}
			this.score += s;
		}

		public void deductScore(int s)
		{
			if (s < 0)
			{
				System.Diagnostics.Debug.WriteLine("Context::deductScore: Deducting negative score? " + s);
			}
			this.score -= s;
		}

		public long getScore()
		{
			return this.score;
		}

		public void resetScore()
		{
			this.score = 0;
		}

		public long getCredits()
		{
			return this.credits;
		}

		public void setCredits(long credits)
		{
			this.credits = credits;
			this.fireMoneyChangedEvent();
		}

		public void setInterest(int start, int increase)
		{
			this.interestStart = start;
			this.interestIncrease = increase;
			this.fireInterestChangedEvent();
		}

		public bool canPay(int amount)
		{
			//System.Diagnostics.Debug.WriteLine("Context::canPay: checking "+amount);
			if (amount > 0)
			{
				return (this.credits >= amount);
			}
			else
			{
				return (this.bonus + this.bonusWait > 0);
			}
		}

		public bool doPay(int amount)
		{
			//System.Diagnostics.Debug.WriteLine("Context::doPay: checking "+amount);
			if (amount > 0)
			{
				if (this.canPay(amount))
				{
					this.credits -= amount;
					//System.Diagnostics.Debug.WriteLine("Context::doPay: credits: "+this.credits);
					this.fireMoneyChangedEvent();
					return true;
				}
				else
				{
					return false;
				}
			}
			else
			{
				if (this.canPay(amount))
				{
					if (this.bonusWait > 0)
					{
						this.bonusWait--;
					}
					else
					{
						this.bonus--;
					}
					this.fireMoneyChangedEvent();
					return true;
				}
				else
				{
					return false;
				}
			}
		}

		public void doReceive(int amount)
		{
			if (amount > 0)
			{
				this.credits += amount;
				this.fireMoneyChangedEvent();
			}
			else
			{
				this.bonusWait++;
				this.fireMoneyChangedEvent();
			}

		}

		public void addTower(Tower t)
		{
			this.towers.Add(t);
			this.fireTowerBuildEvent(t);
		}

		public void removeTower(Tower t)
		{
			this.towers.Remove(t);
			this.fireTowerRemovedEvent(t);
		}

		public void sellTower(Tower t)
		{
			int[] hexC = t.getHex().getLocation();
#if PORT
			this.blackBoxCurrent.sellTower(hexC[0], hexC[1]);
#endif

		}

		public void clearTowers()
		{
#if PORT
			this.mainApp.unSelectTower();
#endif
			Tower t;
			for (int i = this.towers.Count - 1; i >= 0; i--)
			{
				t = this.towers[i];
				Hex hex = t.getHex();
				hex.unSetTower();
				t.doCleanup();
				this.towers.Remove(t);
				this.fireTowerRemovedEvent(t);
			}
		}

		public void nextMode(Tower t, int m)
		{
			int[] hexC = t.getHex().getLocation();
#if PORT
			this.blackBoxCurrent.setMode(hexC[0], hexC[1], m, t.getMode(m) + 1);
#endif
		}

		public void setMode(Tower t, int m, int v)
		{
			int[] hexC = t.getHex().getLocation();
#if PORT
			this.blackBoxCurrent.setMode(hexC[0], hexC[1], m, v);
#endif
		}

		public List<Path> getPaths()
		{
			return this.paths;
		}

		public Path getPath(int p)
		{
			if (p < 0)
			{
				p = 0;
			}
			if (p > this.paths.Count - 1)
			{
				p = this.paths.Count - 1;
			}
			return this.paths[p];
		}

		public int getLives()
		{
			return lives;
		}

		public void removeLife()
		{
			this.lives--;
#if PORT
			if (this.lives <= 0 && this.blackBoxCurrent != null)
			{
				this.blackBoxCurrent.endGame();
			}
#endif
			this.fireLivesChangedEvent();
		}

		public void resetLives(int n)
		{
			this.lives = n;
			this.fireLivesChangedEvent();
		}

		public void addBonus()
		{
			this.bonus++;
			this.fireInterestChangedEvent();
		}

		public void useBonus()
		{
			this.bonus--;
			this.fireInterestChangedEvent();
		}

		public int getInterest()
		{
			return interestStart + this.bonus * this.interestIncrease;
		}

		public int getBonus()
		{
			return bonus + bonusWait;
		}

		public void resetBonus()
		{
			this.bonus = 0;
			this.bonusWait = 0;
			this.fireInterestChangedEvent();
		}

		public void showAlert(string alert)
		{
#if PORT
			this.mainApp.showAlert(alert);
#endif
		}

#if PORT_REM
		public Cache getCache()
		{
			return this.cache;
		}
#endif

#if PORT
		public StyleSheet loadStyleSheet(string name)
		{
			if (this.styleSheets.containsKey(name))
			{
				return this.styleSheets.get(name);
			}
			else
			{
				StyleSheet s = new StyleSheet();
				URL u;
				try
				{
					u = new URL(this.contextUrl, name);
					s.importStyleSheet(u);
				}
				catch (MalformedURLException ex)
				{
					ex.printStackTrace();
				}
				this.styleSheets.put(name, s);
				return s;
			}
		}
#endif

		public void clearImages()
		{
#if PORT_REM
			this.images.Clear();
#endif
		}

#if PORT_REM
		public Image loadImage(string location)
		{
			if (this.images.ContainsKey(location))
			{
				return this.images[location];
			}
			else
			{
				Image i = null;
				string u;
				try
				{
					Uri uri = new Uri(location, UriKind.Relative);
					ImageSource img = new System.Windows.Media.Imaging.BitmapImage(uri);
					Image image = new Image();
					image.SetValue(Image.SourceProperty, img);
				}
				catch (IOException ex)
				{
					System.Diagnostics.Debug.WriteLine("Context::loadImage: IOException reading location: " + location);
				}
				catch (Exception ex)
				{
					System.Diagnostics.Debug.WriteLine(ex.ToString());
				}
				if (i == null)
				{
					i = new Image();
				}
				this.images.Add(location, i);
				return i;
			}
			return null;
		}
#endif

#if PORT_REM
		public Uri loadSvg(string name, string location)
		{
			if (this.svgUniverse == null)
			{
				this.svgUniverse = SVGCache.getSVGUniverse();
			}
			if (!this.svgImages.containsKey(name))
			{
				URL url;
				try
				{
					url = new URL(this.contextUrl, location);
				}
				catch (MalformedURLException ex)
				{
					ex.printStackTrace();
					return null;
				}
				Uri uri = this.svgUniverse.loadSVG(url);
				this.svgImages.put(name, uri);
			}
			return this.svgImages.get(name);
		}
#endif

#if PORT_REM
		public void renderSvgImage(string name, Canvas g, int w, int h)
		{
			RotateTransform oldXform = g.getTransform();
			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			Uri image = this.svgImages.get(name);
			SVGDiagram diagram = svgUniverse.getDiagram(image);
			Rectangle2D.Double rect = new Rectangle2D.Double();
			diagram.getViewRect(rect);

			RotateTransform scaleXform = new RotateTransform();
			//scaleXform.setToTranslation(50,50);
			scaleXform.setToScale(w / rect.width, h / rect.height);
			g.transform(scaleXform);

			//diagram.setDeviceViewport(new Rectangle(0, 0, w, h));
			diagram.setIgnoringClipHeuristic(true);
			try
			{
				diagram.render(g);
			}
			catch (SVGException ex)
			{
				ex.printStackTrace();
			}
			g.setTransform(oldXform);
		}
#endif

		public bool isWaveActive()
		{
			return waveActive;
		}

		public void setWaveActive(bool waveActive)
		{
			this.waveActive = waveActive;
		}
	}
}

